package de.fhkl.imst.i.cgma.raytracer;

import java.awt.Color;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Vector;

import de.fhkl.imst.i.cgma.raytracer.file.*;
import de.fhkl.imst.i.cgma.raytracer.gui.*;

public class Raytracer01_ImplicitSphereIntersection implements IRayTracerImplementation 
{
	
    // viewing volume with infinite end
    private double fovyDegree;
    private double near;
    private double fovyRadians;
    
    // one hardcoded point light as a minimal solution :-(
    private double[] Ia = { 0.25f, 0.25f, 0.25f }; // ambient light color
    private double[] Ids = { 1.0f, 1.0f, 1.0f }; // diffuse and specular light color
    private double[] ICenter = { 4.0f, 4.0f, 2.0f }; // center of point light
    //private double[] ICenter = { 0.0f, 0.0f, 4.0f }; // center of point light
    
	RayTracerGui gui = new RayTracerGui(this);
	
	private int resx, resy;        // viewport resolution
	private double h, w, aspect;   // window height, width and aspect ratio
	
	final int difuseIndex = 3;
	final int ambientIndex = 0;
	final int specularIndex = 6;
	
	Vector<RT_Object> objects;
	
	public void rayTraceThread(int start, int numThreads)
	{
		
	}
	
	private Raytracer01_ImplicitSphereIntersection() 
	{
		try 
		{
		    gui.addObject(RTFileReader.read(I_Sphere.class, new File("data/ikugel.dat")));
		    //gui.addObject(RTFileReader.read(T_Mesh.class, new File("data/dreieck1.dat")));
		    
		    objects = gui.getObjects();
		} 
		catch (IOException e) 
		{
		    e.printStackTrace();
		}
	}
	
	public void setViewParameters(double fovyDegree, double near)
	{
	    // set attributes fovyDegree, fovyRadians, near
		this.fovyDegree = fovyDegree;
		this.near = near;
		this.fovyRadians = fovyDegree / 180 * Math.PI;
			
	    // set attributes resx, resy, aspect
	    resx = gui.getResX();
	    resy = gui.getResY();
	    aspect = (double) resx / resy;

	    // set attributes h, w
	    h = 2 * near * Math.tan(fovyRadians / 2);
	    w = h * aspect; 
	}
	
	@Override
	public void doRayTrace() 
	{
	    double x, y, z;                // intersection point in viewing plane
	    double rayEx, rayEy, rayEz;    // eye point==ray starting point
	    double rayVx, rayVy, rayVz;    // ray vector
	    Color color;
	    
	    // hardcoded viewing volume with fovy and near 
	    setViewParameters(90.0, 1.0);
	    
	    // set eye point
	    rayEx = 0.0;
	    rayEy = 0.0;
	    rayEz = 0.0;
	    
	    z = -1 * near;

	    // DEBUGING
	    //resx=resy=1;
	    
	    // xp, yp: pixel coordinates 
		for(int xp = 0; xp < resx; ++xp) 
		{
			for(int yp = 0; yp < resy; ++yp) 
			{	
						
			    // x, y: view coordinates
			    x = (double) (xp) / (resx - 1) * w - ( (double) w / 2.0d);
			    y = (double) ((resy) - 1.0d - yp) / (resy) * h - ( (double) h/ 2.0d);

			    // DEBUGING
			    //x=y=0;			    
  
			    // ray vector
			    rayVx = x - rayEx;
			    rayVy = y - rayEy;
			    rayVz = z - rayEz;
			   				    
			    // get color or null along the ray
			    color = traceRayAndGetColor(rayEx, rayEy, rayEz, rayVx, rayVy, rayVz);

			    // if color!=null set pixel with color
			    if (color != null)
			    {
			    	gui.setPixel(xp, yp, color.getRGB());
			    }   
			}
		}
	}
	
	// returns Color object or null if no intersection was found
    @SuppressWarnings("unused")
	private Color traceRayAndGetColor(double rayEx, double rayEy, double rayEz, double rayVx, double rayVy, double rayVz) 
    {
        RTFile scene = objects.get(0);
        
    	double minT = Double.MAX_VALUE;
    	int minObjectsIndex = -1;
    	int minIndex = -1;
    	double[] minIP = new double[3];
    	double[] minN = new double[3];
    	float[] minMaterial = new float[3];
    	double minMaterialN = 1;
    	double bu = 0, bv = 0, bw = 1;

    	double[] v = new double[3];
    	double[] l = new double[3];
                
    	// viewing vector at intersection point, normalize!
    	v[0] = -rayVx;
    	v[1] = -rayVy;
    	v[2] = -rayVz;
     
    	normalize(v);
        
        //T_Mesh mesh;
        I_Sphere sphere;
        
        if(scene.getHeader()=="IMPLICIT_SPHERE")
        {   
        	sphere = (I_Sphere)scene;
        
            // intersection uses quadratic equation
        	double emx, emy, emz;
        	
        	emx = rayEx - sphere.center[0];
        	emy = rayEy - sphere.center[1];
        	emz = rayEz - sphere.center[2];
        	
        	double ememScalar = emx * emx + emy * emy + emz * emz;
        	double vvScalar = rayVx * rayVx + rayVy * rayVy + rayVz * rayVz;
        	double vemScalar = rayVx * emx + rayVy * emy + rayVz * emz;
        	
        	// t*t * vvScalar + 2 * t * vemScalar + ememScalar - sphere.radius * sphere.radius;
        	
        	double a, p, q;
        	
        	a = vvScalar;
        	p = (2 * vemScalar) / a;
        	q = (ememScalar - sphere.radius * sphere.radius) / a;
        	
        	double d = p*p - 4*q;
        	
    		// no intersection point? => next object
    		if (d <= 0)
    		    return null;
    		
    		// from here: intersection takes place!

    		// calculate first intersection point with sphere along the ray
    		// x1/2 = - p / 2 +- sqrt( (p/2)^2 - q )
    		double p2 = p / 2.0;
    		double t1 = - p2 + Math.sqrt( p2*p2 - q );
    		double t2 = - p2 - Math.sqrt( p2*p2 - q );
  	
    		double t;
    		
    		if (t2 > t1)
    			t = t1;
    		else
				t = t2;
    		
    		// I'm the winner until now!
    		minT = t;
    		
    		// prepare everything for phong shading

    		// the intersection point s(t) = RayE + t * RayV;
    		minIP[0] = rayEx + t * rayVx;
    		minIP[1] = rayEy + t * rayVy;
    		minIP[2] = rayEz + t * rayVz;
    		
    		// the normal vector at the intersection point, normalize!
    		minN[0] = minIP[0] - sphere.center[0];
    		minN[1] = minIP[1] - sphere.center[1];
    		minN[2] = minIP[2] - sphere.center[2];
    		
    		normalize(minN);
    		
    		// the material
    		minMaterial = sphere.material;
    		minMaterialN = sphere.materialN;
    		//minMaterialN = 100;
    		
    		// intersection: use diffuse material
            //return new Color(sphere.material[difuseIndex], sphere.material[difuseIndex+1], sphere.material[difuseIndex+2]);        
        
        }
        // object is a triangle mesh?
	    else if (scene.getHeader() == "TRIANGLE_MESH") 
	    {
	    	//mesh = (T_Mesh) scene;

	    	return null;
	    } 
	    else
	    {
	    	return null;
	    }
        
    	// light vector at the intersection point, normalize!
        l[0] = ICenter[0] - minIP[0];
    	l[1] = ICenter[1] - minIP[1];
    	l[2] = ICenter[2] - minIP[2];
    	
    	normalize(l);
    	
        // implicit: only phong shading available => shade=illuminate
    	if (scene.getHeader() == "IMPLICIT_SPHERE")
    	{
    	    return phongIlluminate(minMaterial, minMaterialN, l, minN, v, Ia, Ids);
    	}
    	// triangle mesh: flat, gouraud or phong shading according to file data
    	else if (scene.getHeader() == "TRIANGLE_MESH") 
    	{

    	}
        
        
        return null;
    }
    
	// vector normalization
    // CAUTION: vec is an in-/output parameter; the referenced object will be altered!
    private double normalize(double[] vec) 
    {
    	double l = Math.sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);

    	vec[0] = vec[0] / l;
    	vec[1] = vec[1] / l;
    	vec[2] = vec[2] / l;
    	
    	return l;
    }
    
	// calculate phong illumination model with material parameters material and
	// materialN, light vector l, normal vector n, viewing vector v, ambient
	// light Ia, diffuse and specular light Ids
	// return value is a new Color object
	private Color phongIlluminate(float[] material, double materialN, double[] l, double[] n, double[] v, double[] Ia, double[] Ids)
	{
		double ir = 0, ig = 0, ib = 0; // reflected intensity, rgb channels
		double[] r = new double[3]; // reflection vector
		double ln, rv; // scalar products <l,n> and <r,v>
		
		// <l,n>
		ln = l[0] * n[0] + l[1] * n[1] + l[2] * n[2];
		
		// reflection vector r=2*<l,n>*n-l
		r[0] = 2 * ln * n[0] - l[0];
		r[1] = 2 * ln * n[1] - l[1];
		r[2] = 2 * ln * n[2] - l[2];
		
		// <r,v>
		rv = r[0] * v[0] + r[1] * v[1] + r[2] * v[2];
		
		// ambient component, Ia*ra
		ir += Ia[0] * material[ambientIndex];
		ig += Ia[1] * material[ambientIndex+1];
		ib += Ia[2] * material[ambientIndex+2];
		
		// diffuse component, Ids*rd*<l,n>
		if (ln > 0) 
		{
		    ir += Ids[0] * material[difuseIndex] * ln;
		    ig += Ids[1] * material[difuseIndex+1] * ln;
		    ib += Ids[2] * material[difuseIndex+2] * ln;
		}
		
		// specular component, Ids*rs*<r,v>^n
		if (ln > 0 && rv > 0) 
		{
		    double pow = Math.pow(rv, materialN);
		    ir += Ids[0] * material[specularIndex] * pow;
		    ig += Ids[1] * material[specularIndex+1] * pow;
		    ib += Ids[2] * material[specularIndex+2] * pow;
		}
		
		//System.out.println(ir+" "+ig+" "+ib);
		
		if (ir > 1)
			ir = 1.0;
		if (ig > 1)
			ig = 1.0;
		if (ib > 1)
			ib = 1.0;

		return new Color( (float) ir, (float) ig, (float) ib);
	}
    
	public static void main(String[] args) {
		Raytracer01_ImplicitSphereIntersection rt = new Raytracer01_ImplicitSphereIntersection();
		
		//rt.doRayTrace();
	}

	@Override
	public void rayTraceArea(Rectangle area) {
		// TODO Auto-generated method stub
		
	}
}
